using System;
using System.Linq;
using System.Collections.Generic;

using NHibernate.Dialect;


using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Mapping;



using FluentNHibernate.LowercaseSystem;
using FluentNHibernate.RobustCollectionMapper;
 




using NUnit;
using NUnit.Framework;
using FluentNHibernate.Conventions.Helpers;

using FluentNHibernate.Conventions.Helpers.Builders;




namespace TestLowercaseSystem
{
	public static class Helper
	{
		public static void AssertContainsMessage(this string message, string messagePortion)
		{			
			if (message.Contains(messagePortion))
				{}
			else
				Assert.AreEqual(messagePortion, message);
		}
	}
	
	
	[TestFixture]
	public class TestClass
	{

		
		public static string GetClassicScript<T>()
		{
			
				
			return string.Join(";" + Environment.NewLine,
					Fluently.Configure()
						.Database(PostgreSQLConfiguration.Standard)
						.Mappings(m => m.FluentMappings.AddFromNamespaceOf<T>())
						.BuildConfiguration()
						.GenerateSchemaCreationScript(new PostgreSQLDialect()) 
			                       );							
		}
		
		public static string GetRobustScript<T>()
		{
			 
				
			return string.Join(";" + Environment.NewLine,
					Fluently.Configure()
			            
						.Database(PostgreSQLConfiguration.Standard)
						.Mappings(m => 
			                             
			                             m.FluentMappings.AddFromNamespaceOf<T>()
			                             .Conventions.ForLowercaseSystem(referenceSuffix: "_ref", toLowercase: true)	
			                             // .Conventions.Add(ForeignKey.EndsWith("_ref"))
			                             .Conventions.Add(ConventionBuilder.HasMany.Always(x => x.Key.Column( x.NormalizeReference() + "_ref") ))			                             
			                             
			                   		)	 
						.BuildConfiguration()
						.GenerateSchemaCreationScript(new PostgreSQLDialect()) 
                        ); 
			                       
		}
		

		
		
		[Test]
		public void ClassicMappingProducesDoubleReferenceId_Test()
		{			
			
			// Arrange
			
			string s = GetClassicScript<ClassicMappingProducesDoubleReferenceId.Person>();
			
			// Act
			int keywordReferenceCount = s.Split(' ').Count(x => x == "references");
			

			// Assert
			Assert.AreEqual(2, keywordReferenceCount);
			
		}
		
		string expected = "Expected exception was not thrown";
		
		[Test]
		public void ExplicitParentNonExplicitChild_Should_be_thrown()
		{
			string message = "";
			
			try
			{
				// Arrange			
				GetRobustScript<ExplicitParentNonExplicitChild.Person>();
				throw new Exception(expected);
			}
			catch(Exception ex)
			{				
				if (ex.InnerException is RobustReferenceMappingException)
				{
					// Act
					message = ex.InnerException.Message;
					
					// Assert
					message.AssertContainsMessage("Explicitness needed on both ends");
				}
				else
					throw;
			}
			
						
		}
		
		
		
		[Test]
		public void ExplicitParentExplicitChildButKeynameIsNotSame_Should_be_thrown()
		{
			string message = "";
			
			try
			{
				// Arrange			
				GetRobustScript<ExplicitParentExplicitChildButKeynameIsNotSame.Person>();
				throw new Exception(expected);
			}
			
			catch(Exception ex)
			{				
				if (ex.InnerException is RobustReferenceMappingException)
				{
					// Act
					message = ex.InnerException.Message;
					
					// Assert
					message.AssertContainsMessage("doesn't match its key name to parent object");
				}
				else
					throw;
			}
			
			
						
			
			
			
			
			
		}
		
		[Test]
		public void ExplicitParentExplicitChildrenButKeynameIsNotSame_Should_be_thrown()
		{
			string message = "";
			
			try
			{
				// Arrange			
				GetRobustScript<ExplicitParentExplicitChildrenButKeynameIsNotSame.Person>();
				throw new Exception(expected);
			}
			catch(Exception ex)
			{				
				if (ex.InnerException is RobustReferenceMappingException)
				{
					// Act
					message = ex.InnerException.Message;
					
					// Assert
					message.AssertContainsMessage("doesn't match any key to parent object");
				}
				else
					throw;
			}
			
			
						
		}
		
		[Test]	
		public void ResolvesToOneReferenceIdOnly_Test()
		{									
			// Arrange			
			string s = GetRobustScript<ResolvesToOneReferenceIdOnly.Person>();
			

			// Act
			int keywordReferenceCount = s.Split(' ').Count(x => x == "references");
			
			
			
			// Assert
			Assert.AreEqual(1, keywordReferenceCount);
			
		}
		
		
		
		[Test]
		public void AmbiguousCollection_Should_be_thrown()
		{									
			
			string message = "";
			
			try
			{
				// Arrange			
				GetRobustScript<AmbiguousCollection.Person>();
				throw new Exception(expected);
			}
			catch(Exception ex)
			{				
				if (ex.InnerException is RobustReferenceMappingException)
				{
					// Act
					message = ex.InnerException.Message;
					
					// Assert
					message.AssertContainsMessage("Ambiguous collection found");
				}
				else
					throw;
			}
			
			
						
		}
		
		[Test]
		public void NonExplicitParentExplicitChild_Should_be_thrown()
		{
			string message = "";
			
			try
			{
				// Arrange			
				GetRobustScript<NonExplicitParentExplicitChild.Person>();
				throw new Exception(expected);
			}
			catch(Exception ex)
			{
				if (ex.InnerException is RobustReferenceMappingException)
				{
					// Act
					message = ex.InnerException.Message;
					
					
					// Assert
					message.AssertContainsMessage("has explicit Reference while the parent object");
					
				}
				else
					throw;
				

			}
						
		}
		
		
		[Test]
		public void AmbiguousReference_Should_be_thrown()
		{
			string message = "";
			
			try
			{
				// Arrange			
				GetRobustScript<AmbiguousReference.Person>();
				throw new Exception(expected);
			}
			catch(Exception ex)
			{
				if (ex.InnerException is RobustReferenceMappingException)
				{
					// Act
					message = ex.InnerException.Message;
					
					
					// Assert
					message.AssertContainsMessage("Ambiguous references found");
					
				}
				else
					throw;
				

			}
			
		}
		
	}//TestClass
	
	
}

namespace ResolvesToOneReferenceIdOnly
{
	public class Person
	{
		public int PersonId { get; set; }
		public string PersonName { get; set; }
		public IList<Contact> Contacts { get; set; }
	}
	
	public class Contact
	{
		public Person Owner { get; set; }
		public int ContactId { get; set; }
		public string Number { get; set; }
		public string ContactType { get; set; }
	}
	
	
	public class PersonMap : ClassMapExt<Person>
	{
		public PersonMap()
		{
			Id(x => x.PersonId);
			
			Map(x => x.PersonName);
			
			HasMany(x => x.Contacts);
		}
	}
	
	public class ContactMap : ClassMapExt<Contact>
	{
		public ContactMap()
		{
			References(x => x.Owner);
			
			Id(x => x.ContactId);
			
			Map(x => x.Number);
			Map(x => x.ContactType);
		}		
	}
	
}


namespace AmbiguousReference
{
	public class Person
	{
		public int PersonId { get; set; }
		public string PersonName { get; set; }
		public IList<Contact> Contacts { get; set; }
	}
	
	public class Contact
	{
		public Person Owner { get; set; }
		public Person OldOwner { get; set; }
		public int ContactId { get; set; }
		public string Number { get; set; }
		public string ContactType { get; set; }
	}
	
	
	public class PersonMap : ClassMapExt<Person>
	{
		public PersonMap()
		{
			Id(x => x.PersonId);
			
			Map(x => x.PersonName);
			
			HasMany(x => x.Contacts);
		}
	}
	
	public class ContactMap : ClassMapExt<Contact>
	{
		public ContactMap()
		{
			References(x => x.Owner);
			References(x => x.OldOwner);
			
			Id(x => x.ContactId);
			
			Map(x => x.Number);
			Map(x => x.ContactType);
		}		
	}
	
}




namespace AmbiguousCollection
{
	public class Person
	{
		public int PersonId { get; set; }
		public string PersonName { get; set; }
		public IList<Contact> Contacts { get; set; }
		public IList<Contact> GrayContacts { get; set; }
	}
	
	public class Contact
	{
		public Person Owner { get; set; }
		public int ContactId { get; set; }
		public string Number { get; set; }
		public string ContactType { get; set; }
	}
	
	
	public class PersonMap : ClassMapExt<Person>
	{
		public PersonMap()
		{
			Id(x => x.PersonId);
			
			Map(x => x.PersonName);
			
			HasMany(x => x.Contacts);
			HasMany(x => x.GrayContacts);
		}
	}
	
	public class ContactMap : ClassMapExt<Contact>
	{
		public ContactMap()
		{
			References(x => x.Owner);
			
			Id(x => x.ContactId);
			
			Map(x => x.Number);
			Map(x => x.ContactType);
		}		
	}
	
}



namespace ClassicMappingProducesDoubleReferenceId
{
	public class Person
	{
		public int PersonId { get; set; }
		public string PersonName { get; set; }
		public IList<Contact> Contacts { get; set; }
	}
	
	public class Contact
	{
		public Person Owner { get; set; }
		public int ContactId { get; set; }
		public string Number { get; set; }
		public string ContactType { get; set; }
	}
	
	
	public class PersonMap : ClassMap<Person>
	{
		public PersonMap()
		{
			Id(x => x.PersonId);
			
			Map(x => x.PersonName);
			
			HasMany(x => x.Contacts);
		}
	}
	
	public class ContactMap : ClassMap<Contact>
	{
		public ContactMap()
		{
			References(x => x.Owner);
			
			Id(x => x.ContactId);
			
			Map(x => x.Number);
			Map(x => x.ContactType);
		}		
	}

	
}


namespace ExplicitParentNonExplicitChild
{
	public class Person
	{
		public int PersonId { get; set; }
		public string PersonName { get; set; }
		public IList<Contact> Contacts { get; set; }
	}
	
	public class Contact
	{
		public Person Owner { get; set; }
		public int ContactId { get; set; }
		public string Number { get; set; }
		public string ContactType { get; set; }
	}
	
	
	public class PersonMap : ClassMapExt<Person>
	{
		public PersonMap()
		{
			Id(x => x.PersonId);
			
			Map(x => x.PersonName);
			
			HasMany(x => x.Contacts).KeyColumn("owner_id");
		}
	}
	
	public class ContactMap : ClassMapExt<Contact>
	{
		public ContactMap()
		{
			References(x => x.Owner);
			
			Id(x => x.ContactId);
			
			Map(x => x.Number);
			Map(x => x.ContactType);
		}		
	}	
}



namespace ExplicitParentExplicitChildButKeynameIsNotSame
{
	public class Person
	{
		public int PersonId { get; set; }
		public string PersonName { get; set; }
		public IList<Contact> Contacts { get; set; }
	}
	
	public class Contact
	{
		public Person Owner { get; set; }
		public int ContactId { get; set; }
		public string Number { get; set; }
		public string ContactType { get; set; }
	}
	
	
	public class PersonMap : ClassMapExt<Person>
	{
		public PersonMap()
		{
			Id(x => x.PersonId);
			
			Map(x => x.PersonName);
			
			HasMany(x => x.Contacts).KeyColumn("owner_id");
		}
	}
	
	public class ContactMap : ClassMapExt<Contact>
	{
		public ContactMap()
		{
			References(x => x.Owner).Column("pawner_id");
			
			Id(x => x.ContactId);
			
			Map(x => x.Number);
			Map(x => x.ContactType);
		}		
	}	
}

namespace ExplicitParentExplicitChildrenButKeynameIsNotSame
{
	public class Person
	{
		public int PersonId { get; set; }
		public string PersonName { get; set; }
		public IList<Contact> Contacts { get; set; }
	}
	
	public class Contact
	{
		public Person Owner { get; set; }
		public Person OldOwner { get; set; }
		public int ContactId { get; set; }
		public string Number { get; set; }
		public string ContactType { get; set; }
	}
	
	
	public class PersonMap : ClassMapExt<Person>
	{
		public PersonMap()
		{
			Id(x => x.PersonId);
			
			Map(x => x.PersonName);
			
			HasMany(x => x.Contacts).KeyColumn("owner_id");
		}
	}
	
	public class ContactMap : ClassMapExt<Contact>
	{
		public ContactMap()
		{
			References(x => x.Owner).Column("pawner_id");
			References(x => x.OldOwner).Column("prevowner_id");
			
			Id(x => x.ContactId);
			
			Map(x => x.Number);
			Map(x => x.ContactType);
		}		
	}	
}

namespace NonExplicitParentExplicitChild
{
	public class Person
	{
		public int PersonId { get; set; }
		public string PersonName { get; set; }
		public IList<Contact> Contacts { get; set; }
	}
	
	public class Contact
	{
		public Person Owner { get; set; }
		public int ContactId { get; set; }
		public string Number { get; set; }
		public string ContactType { get; set; }
	}
	
	
	public class PersonMap : ClassMapExt<Person>
	{
		public PersonMap()
		{
			Id(x => x.PersonId);
			
			Map(x => x.PersonName);
			
			HasMany(x => x.Contacts);
		}
	}
	
	public class ContactMap : ClassMapExt<Contact>
	{
		public ContactMap()
		{
			References(x => x.Owner).Column("pawner_id");;
			
			Id(x => x.ContactId);
			
			Map(x => x.Number);
			Map(x => x.ContactType);
		}		
	}		
}